#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>  /* for sockaddr_in and inet_addr() */

#define MAX(x,y) ((x) > (y) ? (x) : (y))
#define BUF_SIZE 256
#define N_CLIENTS 10
#define STR_LEN 5

typedef struct Client_t
{
    int     m_fd            ;
    size_t  m_totRd         ;
    size_t  m_totWr         ;
    char    m_rBuf[BUF_SIZE];
    char    m_wBuf[BUF_SIZE];
}Client;

int SocketServer(void)
{
    int                 sockFd      ;
    int yes;
    struct sockaddr_in  serverAddr  ;

    sockFd = socket(AF_INET,        /*  Specifies the communications domain in which a socket is to be created                          */
                   SOCK_STREAM ,    /*  Specifies the type of socket to be created.                                                     */
                   IPPROTO_TCP);    /*  Specifies a particular protocol to be used with the socket. Specifying a protocol of 0 causes
                                        socket() to use an unspecified default protocol appropriate for the requested socket type.      */
    if(sockFd == -1)
    {
        perror("socket");
        return -1;
    }

    /* Construct the server sockaddr_in structure */
    memset(&serverAddr, 0, sizeof(serverAddr));             /* Clear struct */
    serverAddr.sin_family = AF_INET;                        /* Internet/IP  */
    serverAddr.sin_addr.s_addr =inet_addr("127.0.0.1");    /* IP address   */
    serverAddr.sin_port = 7;                                /* server port  */

    yes = 1;
           if (setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR,
                   (char *) &yes, sizeof(yes)) == -1) {
               perror("setsockopt");
               close(sockFd);
               return -1;
           }

    if(bind(sockFd,                             /*  socket file descriptor                                  */
            (struct sockaddr *)&serverAddr,     /*  assigns the address specified to the socket             */
            sizeof(struct sockaddr_in)) == -1)  /*  specifies the size, in bytes, of the address structure  */
    {
        perror("bind");
        close(sockFd);
        return -1;
    }

    if(listen(sockFd, 5))
    {
        perror("listen");
        close(sockFd);
        return -1;
    }

    return sockFd;
}

int ServerAccept(int _serverFd)
{
    int                 connectFd           ;
    socklen_t           clientAddrSize      ;
    struct sockaddr_in  clientAddr          ;


    clientAddrSize = sizeof(struct sockaddr_in);
    connectFd = accept(_serverFd,                      /*  the binding socket fd       */
                 (struct sockaddr *) &clientAddr,   /*  gets the client addr struct */
                 &clientAddrSize);                  /*  size of the excpted struct  */
    if (connectFd == -1)
    {
        perror("accept");
        return -1;
    }
    if(clientAddrSize != sizeof(struct sockaddr_in))
    {
        fprintf(stderr, "error in size of client addres\n");
        close(connectFd);
        return -1;
    }

    return connectFd;
}

Client* CreateClientStruct(int _clientFd)
{
    Client* client = (Client*)malloc(sizeof(Client));
    if(!client)
    {
        fprintf(stderr, "error in allocating memory\n");
        return 0;
    }

    client->m_fd = _clientFd;
    client->m_totRd = 0;
    client->m_totWr = 0;
    strcpy(client->m_wBuf, "Hello");

    return client;
}

int WriteSocket(int _sockFd, char* _sendBuf, size_t* _totalBytesSnt, size_t _wrLen)
{
    size_t bytesSnt;

    /*while(*_totalBytesSnt < _wrLen)
    {*/
        if((bytesSnt = send(_sockFd, _sendBuf + *_totalBytesSnt, _wrLen - *_totalBytesSnt , 0)) == -1)
        {
            perror("send");
            return -1;
        }
        *_totalBytesSnt += bytesSnt;
    /*}*/
    /**_totalBytesSnt = 0;*/

    return 0;
}

int RecvSocket(int _sockFd, char* _recvBuf, size_t* _totalBytesRcv, size_t _rdLen)
{
   size_t bytesRcv;


        if((bytesRcv = recv(_sockFd, _recvBuf + *_totalBytesRcv, _rdLen - *_totalBytesRcv, 0)) <= 0)
        {
            perror("recv");
            return -1;
        }
        *_totalBytesRcv += bytesRcv;
        /*if(*_totalBytesRcv == _rdLen)
        {
            *_totalBytesRcv = 0;
        }*/
    *(_recvBuf + _rdLen) = '\0';
    printf("%s\n", _recvBuf);

    return 0;
}

void RmvClient(Client** _cArr, int _indx, int* _nClients)
{
    free(_cArr[_indx]);
    for(;_indx < *_nClients - 1;++_indx)
    {
        _cArr[_indx] = _cArr[_indx + 1];
    }
    --*_nClients;
}



int SocketSelect(int _serverFd)
{
    fd_set readFds;
    fd_set writeFds;
    fd_set expFds;
    int n = 200;
    int nfds = _serverFd;
    int setFds;
    int clientFd;
    int nClients = 0;
    int i;
    Client* clientArr[N_CLIENTS];

    while(n)
    {
        FD_ZERO(&readFds);
        FD_ZERO(&writeFds);
        FD_ZERO(&expFds);
        nfds = 0;

        FD_SET(_serverFd, &readFds);        /*  set server fd as read => if there is a new connection accept it.                            */
        nfds = MAX(_serverFd, nfds);

        for(i = 0; i < nClients; ++i)
        {
            if(clientArr[i]->m_totRd < STR_LEN)
            {
                FD_SET(clientArr[i]->m_fd, &readFds);
                nfds = MAX(clientArr[i]->m_fd, nfds);
            }
            else
            {
                clientArr[i]->m_totRd = 0;
            }
            if(clientArr[i]->m_totWr < STR_LEN)
            {
                FD_SET(clientArr[i]->m_fd, &writeFds);
                nfds = MAX(clientArr[i]->m_fd, nfds);
            }
            else
            {
                clientArr[i]->m_totWr = 0;
            }
            FD_SET(clientArr[i]->m_fd, &expFds);
        }

        setFds = select(nfds + 1,           /*  one more than the maximum of any file descriptor in any of the sets                         */
                        &readFds,           /*  set is watched to see if data is available for reading from any of its file descriptors.    */
                        &writeFds,          /*  set  is watched to see if there is space to write data to any of its file descriptors.      */
                        &expFds,            /*  set is watched for "exceptional conditions".                                                */
                        0         );        /*  timeout of struct timeval, time select() may wait before returning                          */
        if(setFds == -1)
        {
            perror("select");
            return 1;
        }
        printf("%d\n", setFds);

        if(FD_ISSET(_serverFd, &readFds))
        {
            clientFd = ServerAccept(_serverFd);
            if(clientFd == -1)
            {
                fprintf(stderr, "failed to establish connection\n");
            }
            clientArr[nClients] = CreateClientStruct(clientFd);
            ++nClients;
            --setFds;
        }

        for(i = 0; setFds; ++i)
        {
            if(FD_ISSET(clientArr[i]->m_fd, &readFds))
            {
                if(RecvSocket(clientArr[i]->m_fd, clientArr[i]->m_rBuf, &(clientArr[i]->m_totRd), STR_LEN) == -1)
                {
                    shutdown(clientArr[i]->m_fd, SHUT_RDWR);
                    close(clientArr[i]->m_fd);
                    RmvClient(clientArr ,i , &nClients);
                    --i;
                    --setFds;
                    continue;
                }
                --setFds;
            }
            if(!setFds)
            {
                break;
            }

            if(FD_ISSET(clientArr[i]->m_fd, &writeFds))
            {
                if(WriteSocket(clientArr[i]->m_fd, clientArr[i]->m_wBuf, &(clientArr[i]->m_totWr), STR_LEN)== -1)
                {
                    shutdown(clientArr[i]->m_fd, SHUT_RDWR);
                    close(clientArr[i]->m_fd);
                    RmvClient(clientArr ,i , &nClients);
                    --i;
                    --setFds;
                    continue;
                }
                --setFds;
            }
            if(!setFds)
            {
                break;
            }

            if(FD_ISSET(clientArr[i]->m_fd, &expFds))
            {
                printf("FFFFF\n");
                --setFds;
            }
            if(!setFds)
            {
                break;
            }

        }
        --n;
    }
    return 0;
}


int main()
{
    int sockFd;

    sockFd = SocketServer();
    SocketSelect(sockFd);

    printf("end of program\n");
    return 0;
}
